﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Viyi.Util.Codec
{
    public class Base64Codec : TextCodec, ITextCodec
    {
        const int reverseBase64CodeOffset = 43;
        static readonly byte[] reverseBase64Code =
        {                                               // ASCII offset 43
            62, 0xff, 0xff, 0xff, 63,                   // [+~/] 43~47 (count 5)
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61,     // [0~9] 48~57 (count 10)
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,   // ASCII 58~64 (count 7)
            0, 1, 2, 3, 4, 5, 6,                        // [A~G] 65~71 (count 7)
            7, 8, 9, 10, 11, 12, 13,                    // [H~N] 72~78 (count 7)
            14, 15, 16, 17, 18, 19,                     // [O~T] 79~84 (count 6)
            20, 21, 22, 23, 24, 25,                     // [U~Z] 85~90 (count 6) 
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff,         // ASCII 91~96
            26, 27, 28, 29, 30, 31, 32,                 // [a~g] 97~103 (count 7)
            33, 34, 35, 36, 37, 38, 39,                 // [h~n] 104~110 (count 7)
            40, 41, 42, 43, 44, 45,                     // [o~t] 111~116 (count 6)
            46, 47, 48, 49, 50, 51,                     // [u~z] 117~122 (count 6) 
        };

        static readonly char[] base64Chars = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
            'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
            'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '='
        };

        public override byte[] Decode(string code) => Convert.FromBase64String(code);

        public override void Decode(Stream outStream, IEnumerable<string> lines)
        {
            new Decoder(outStream).Decode(lines);
        }

        protected override void Encode(ICharWriter writer, IEnumerable<byte[]> data)
        {
            new Encoder(writer).Encode(data);
        }

        sealed class Decoder
        {
            const int bufferSize = 4;
            readonly Stream stream;
            readonly char[] buffer = new char[bufferSize];
            int bufferIndex;

            static bool isValidChar(char ch) => ch < 43 || ch > 122
                ? false
                : reverseBase64Code[ch - reverseBase64CodeOffset] != byte.MaxValue;

            internal Decoder(Stream outStream)
            {
                stream = outStream;
            }

            internal void Decode(IEnumerable<string> lines)
            {
                foreach (var line in lines)
                {
                    Decode(line);
                }

                switch (bufferIndex)
                {
                    case 1:
                        throw new FormatException("input is not complate base64 text");
                    case 2:
                        Decode(buffer[0], buffer[1]);
                        break;
                    case 3:
                        Decode(buffer[0], buffer[1], buffer[2]);
                        break;
                }
            }

            void Decode(string line)
            {
                // 本方法的详细注释参考 `Encoder.Encode(byte[])`

                if (string.IsNullOrEmpty(line))
                {
                    return;
                }

                foreach (var ch in line)
                {
                    if (!isValidChar(ch))
                    {
                        continue;
                    }

                    buffer[bufferIndex++] = ch;
                    if (bufferIndex >= bufferSize)
                    {
                        DecodeBuffer();
                        bufferIndex = 0;
                    }
                }
            }

            void DecodeBuffer()
            {
                int v = (reverseBase64Code[buffer[0] - reverseBase64CodeOffset] << 18)
                    | (reverseBase64Code[buffer[1] - reverseBase64CodeOffset] << 12)
                    | (reverseBase64Code[buffer[2] - reverseBase64CodeOffset] << 6)
                    | reverseBase64Code[buffer[3] - reverseBase64CodeOffset];
                stream.WriteByte((byte) (v >> 16));
                stream.WriteByte((byte) ((v >> 8) & 0xff));
                stream.WriteByte((byte) (v & 0xff));
            }

            void Decode(char ch1, char ch2)
            {
                stream.WriteByte((byte)
                    ((reverseBase64Code[ch1 - reverseBase64CodeOffset] << 2)
                    | (reverseBase64Code[ch2 - reverseBase64CodeOffset] >> 4)));
            }

            void Decode(char ch1, char ch2, char ch3)
            {
                int v = (reverseBase64Code[ch1 - reverseBase64CodeOffset] << 10)
                    | (reverseBase64Code[ch2 - reverseBase64CodeOffset] << 4)
                    | (reverseBase64Code[ch3 - reverseBase64CodeOffset] >> 2);
                stream.WriteByte((byte) (v >> 8));
                stream.WriteByte((byte) (v & 0xff));
            }
        }

        sealed class Encoder
        {
            const int bufferSize = 3;
            readonly ICharWriter writer;
            readonly byte[] buffer = new byte[bufferSize];
            int bufferIndex;

            internal Encoder(ICharWriter writer)
            {
                this.writer = writer;
            }

            internal void Encode(IEnumerable<byte[]> data)
            {
                foreach (var segment in data)
                {
                    Encode(segment);
                }

                // 如果 bufferIndex 大于 0（必定小于 3），需要收尾
                switch (bufferIndex)
                {
                    case 1:
                        EncodeLast(buffer[0]);
                        break;
                    case 2:
                        EncodeLast(buffer[0], buffer[1]);
                        break;
                }
            }

            void Encode(byte[] segment)
            {
                if (segment == null || segment.Length == 0)
                {
                    return;
                }

                // i 指示在 segment 中的位置，贯穿始终
                int i = 0;

                // 如果上个 segment 没处理完，bufferIndex 表示上次剩下的字节数
                // 这里需要先将 buffer 填满
                if (bufferIndex > 0)
                {
                    while (bufferIndex < bufferSize && i < segment.Length)
                    {
                        buffer[bufferIndex++] = segment[i++];
                    }

                    // 如果 segment 太小，仍然没把上个 segment 留下的 buffer 填满，
                    // 只好等下一个 segment 来接着填
                    if (bufferIndex < bufferSize)
                    {
                        return;
                    }

                    // 填满了 buffer，Encode 处理掉，再将 bufferIndex 标记为 0，表示当前无 buffer
                    EncodeMiddle(buffer);
                    bufferIndex = 0;
                }

                // 因为每次都是处理 3 个字节，所以最后一个起始位置一定是至少比字节组长度少 3
                int lastStart = segment.Length - bufferSize;
                for (; i <= lastStart; i += bufferSize)
                {
                    EncodeMiddle(segment, i);
                }

                // 如果 i 没达到 segment.Length，说明还有不足 3 个字节需要处理
                // 存入 buffer 中
                for (; i < segment.Length; i++)
                {
                    buffer[bufferIndex++] = segment[i];
                }
            }

            void EncodeMiddle(byte[] data, int start = 0)
            {
                EncodeMiddle(data[start], data[start + 1], data[start + 2]);
            }

            void EncodeMiddle(byte b1, byte b2, byte b3)
            {
                int combo = (b1 << 16) | (b2 << 8) | b3;
                writer.Write(base64Chars[combo >> 18]);
                writer.Write(base64Chars[(combo >> 12) & 0x3f]);
                writer.Write(base64Chars[(combo >> 6) & 0x3f]);
                writer.Write(base64Chars[combo & 0x3f]);
            }

            void EncodeLast(byte b1, byte b2)
            {
                int combo = (b1 << 8) | b2;
                writer.Write(base64Chars[combo >> 10]);
                writer.Write(base64Chars[(combo >> 4) & 0x3f]);
                writer.Write(base64Chars[(combo << 2) & 0x3f]);
                writer.Write(base64Chars[64]);
                writer.Flush();
            }

            void EncodeLast(byte b1)
            {
                writer.Write(base64Chars[b1 >> 2]);
                writer.Write(base64Chars[(b1 << 4) & 0x3f]);
                writer.Write(base64Chars[64]);
                writer.Write(base64Chars[64]);
                writer.Flush();
            }
        }
    }
}
